// Written by Dale J Williams.
// Licensed under MPL 1.1 unless otherwise specified. 
// http://www.mozilla.org/MPL/MPL-1.1.html

package engineDJW.display {
	
	import engineDJW.interfaces.IWidget;
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	
	/**
	 * The Widget class is a blank container designed to override inbuilt resizing 
	 * methods. A Widget can be extended in place of a Sprite where redrawing is to
	 * be automatically handled.
	 * 
	 * Widgets are practical for the creation of scalable user interface elements.
	 * 
	 * @author Dale J Williams
	 */
	public class Widget extends Sprite implements IWidget {

		protected var _width				:Number		= 0;
		protected var _height				:Number		= 0;
		private var _onDraw					:Function	= null;
		private var _stageResizable			:Boolean;
		private var _onAddedToStage			:Function;
		private var _onRemovedFromStage		:Function;
		private var _managedListeners		:Array;
		
		/**
		 * Widgets are designed to be blank on instanciation. Though a drawing
		 * function can be set at this stage, it is not required or recommended.
		 * 
		 * @param	on_draw		Will set the drawing function on instanciation. This
		 * is not required and can set later using the <code>onDraw</code> setting.
		 */
		public function Widget(on_draw:Function = null) {
			if(on_draw != null)
				onDraw = on_draw;
		}
		
		/**
		* drawNextRender can be called whenever a change requires the Widget to need 
		* to redraw. This is the recommended drawing function.
		* 
		* The drawNextRender function will call the onDraw function, if set, on next
		* render.
		*/
		public function drawNextRender():void {
			if (!stage) return
			super.addEventListener(Event.RENDER, _callDraw, false, 0, true);
			super.addEventListener(Event.ENTER_FRAME, _callDraw, false, 0, true);
			stage.invalidate();
		}
		
		public function cancelNextRender():void {
			if (!stage) return;
			if (hasEventListener(Event.RENDER)) super.removeEventListener(Event.RENDER, _callDraw);
			if (hasEventListener(Event.ENTER_FRAME)) super.removeEventListener(Event.ENTER_FRAME, _callDraw);
		}
		
		/**
		* Forces the onDraw function to be called immediately. Calling redraw instead 
		* of drawNextRender will force the visual update before the next render. This
		* method will override stage management.
		*/
		public function redraw():void {
			cancelNextRender();
			if (_onDraw != null)
				_onDraw();
		}
		
		public function manageEventListener(type:String, listener:Function, target:Object = null):void {
			if (!target) target = this;
			if (!_managedListeners) _managedListeners = [];
			_managedListeners.push([type, listener, target]);
			if (stage) target.addEventListener(type, listener);
			_manageStageFunctions();
		}
		
		public function unmanageEventListener(type:String, listener:Function, target:Object = null):void {
			if (!target) target = this;
			if (target.hasEventListener(type)) {		
				if (_managedListeners) {
					var managedListener:Object;
					for (var i:int = 0; i < _managedListeners.length; i++) {
						managedListener = _managedListeners[i];
						if (managedListener[0] == type && managedListener[1] == listener)
						_managedListeners.splice(i, 1);
						target.removeEventListener(type, listener);
					}
				}
			}
		}
		
		public function sizeTo(target:DisplayObject):void {
			_width = target.width;
			_height = target.height;
			drawNextRender();
		}
		
		public function sizeToStage($drawNextRender:Boolean=false):void {
			_width = stage.stageWidth;
			_height = stage.stageHeight;
			if ($drawNextRender) drawNextRender();
		}
		
		private function _callDraw(e:Event=null):void {
			if (e) cancelNextRender();
			if (!stage) return;
			if (_onDraw != null) _onDraw();
		}
		
		private function _manageStageResize(e:Event=null):void {
			if (stage && _stageResizable)
				stage.addEventListener(Event.RESIZE, _callDraw);
			else if (e) stage.removeEventListener(Event.RESIZE, _callDraw);
		}
		
		/**
		* If set to true, the Widget will automatically call the drawing function.
		*/
		public function set stageResizable(value:Boolean):void {
			_stageResizable = value;
			if(value){
				super.addEventListener(Event.ADDED_TO_STAGE, _manageStageResize);
				super.addEventListener(Event.REMOVED_FROM_STAGE, _manageStageResize);
			} else {
				super.removeEventListener(Event.ADDED_TO_STAGE, _manageStageResize);
				super.removeEventListener(Event.REMOVED_FROM_STAGE, _manageStageResize);
			}
			_manageStageResize();
		}
		
		/**
		 * onDraw is the drawing function. The drawing function is called whenever 
		 * Widget renders, provided it is set. It is good practice to set this value
		 * in your Widget's constructor.
		 */
		public function set onDraw(value:Function):void {
			if(value != null) 
				super.addEventListener(Event.ADDED_TO_STAGE, _callDraw);
			else super.removeEventListener(Event.ADDED_TO_STAGE, _callDraw);
			_onDraw = value;
			_callDraw();
		}		
		
		private function _callAddedToStage(e:Event = null):void {
			for each(var managedListener:Array in _managedListeners)
				managedListener[2].addEventListener(managedListener[0], managedListener[1]);
			if (_onAddedToStage != null)
				_onAddedToStage();
		}
		
		private function _callRemovedFromStage(e:Event = null):void {
			for each(var managedListener:Array in _managedListeners)
				managedListener[2].removeEventListener(managedListener[0], managedListener[1]);
			if (_onRemovedFromStage != null)
				_onRemovedFromStage();
		}
		
		private function _manageStageFunctions(e:Event=null):void {
			addEventListener(Event.ADDED_TO_STAGE, _callAddedToStage);
			addEventListener(Event.REMOVED_FROM_STAGE, _callRemovedFromStage);
		}
		
		/**
		 * The onAddedToStage method is called when the StageWidget is added to the
		 * stage, provided it is set. It is good practice to set this value
		 * in your StageWidget's constructor.
		 */
		public function set onAddedToStage(value:Function):void {
			_onAddedToStage = value;
			_manageStageFunctions();
		}
		
		/**
		 * The onRemovedFromStage method is called when the StageWidget is added to 
		 * the stage, provided it is set. It is good practice to set this value
		 * in your StageWidget's constructor.
		 */
		public function set onRemovedFromStage(value:Function):void {
			_onRemovedFromStage = value;
			_manageStageFunctions();
		}
		
		/**
		 * A Widget's width value must be set by either itself or its container. 
		 * 
		 * Without a width and height a Widget is invisible. The behaviour of width
		 * overrides Sprite's default width behaviour.
		 * 
		 * Setting width will cause callDrawNextRender to be called.
		 */
		override public function get width():Number { return _width; }
		override public function set width(value:Number):void {
			_width = value;
			drawNextRender();
		}	
		
		/**
		 * A Widget's height value must be set by either itself or its container. 
		 * 
		 * Without a height and width a Widget is invisible. The behaviour of height
		 * overrides Sprite's default height behaviour.
		 * 
		 * Setting height will cause drawNextRender to be called.
		 */
		override public function get height():Number { return _height; }
		override public function set height(value:Number):void {
			_height = value;
			drawNextRender();
		}	
		
		/**
		 * The superWidth value can be used to access the Sprite's native width parameter. 
		 * 
		 * This will access the player's native scaling behaviour and should not be used at 
		 * the same time as the overriden width value.
		 */
		public function get superWidth():Number { return super.width; }
		public function set superWidth(value:Number):void {
			super.width = value;
		}	
		
		/**
		 * The superHeight value can be used to access the Sprite's native height parameter. 
		 * 
		 * This will access the player's native scaling behaviour and should not be used at 
		 * the same time as the overriden height value.
		 */
		public function get superHeight():Number { return super.height; }
		public function set superHeight(value:Number):void {
			super.height = value;
		}
		
	}
	
}